#include "stream_reassembler.hh"

// Dummy implementation of a stream reassembler.

// For Lab 1, please replace with a real implementation that passes the
// automated checks run by `make check_lab1`.

// You will need to add private members to the class declaration in `stream_reassembler.hh`

template<typename... Targs>
void DUMMY_CODE(Targs &&.../* unused */) {}

using namespace std;

StreamReassembler::StreamReassembler(const size_t capacity) : _output(capacity), _capacity(capacity) {}

//! \details This function accepts a substring (aka a segment) of bytes,
//! possibly out-of-order, from the logical stream, and assembles any newly
//! contiguous substrings and writes them into the output stream in order.
void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {
    size_t end_of_str = index + data.length();
    if (eof) {
        _eof_idx = end_of_str;
        if (_eof_idx == _cur_idx) {
            reach_eof();
            return;
        }
    }
    if (index >= _capacity + _cur_idx || end_of_str <= _cur_idx)
        return;
    size_t start_point = _cur_idx > index ? _cur_idx - index : 0UL;

    size_t end_point = min(_cur_idx + _capacity - index, data.length());

    size_t abs_idx = start_point == 0 ? index : _cur_idx;
    string data_to_push(data.begin() + start_point, data.begin() + end_point);
    if (!_unassembled_strings.empty()) {
        auto lb = _unassembled_strings.lower_bound(abs_idx);
        auto prev_it = lb;
        if (prev_it != _unassembled_strings.begin()) {
            --prev_it;
            long long diff = (prev_it->first + prev_it->second.length() - abs_idx);
            if (diff >= 0) {
                size_t data_len = data_to_push.length();
                _unassembled_bytes -= prev_it->second.length();
                data_to_push =
                        move(prev_it->second) +
                        data_to_push.substr(diff > static_cast<int>(data_len) ? data_len : diff);
                abs_idx = prev_it->first;
                _unassembled_strings.erase(prev_it);
            }
        }
        while (lb != _unassembled_strings.end()) {
            long long diff = abs_idx + data_to_push.length() - lb->first;
            if (diff >= 0) {
                size_t lblen = lb->second.length();
                _unassembled_bytes -= lblen;
                data_to_push += lb->second.substr(diff > static_cast<int>(lblen) ? lblen : diff);
                _unassembled_strings.erase(lb);
                lb = _unassembled_strings.lower_bound(abs_idx);
            } else {
                break;
            }
        }
    }

    _unassembled_strings[abs_idx] = data_to_push;
    _unassembled_bytes += data_to_push.length();

    if (not _unassembled_strings.empty() && _unassembled_strings.begin()->first == _cur_idx) {
        auto it = _unassembled_strings.begin();
        _unassembled_bytes -= it->second.length();
        size_t bytes_writing = _output.write(it->second);
        _cur_idx += bytes_writing;
        if (_cur_idx == _eof_idx) {
            reach_eof();
            return;
        }
        _unassembled_strings.erase(it);
    }
}

size_t StreamReassembler::unassembled_bytes() const { return _unassembled_bytes; }

bool StreamReassembler::empty() const { return _unassembled_strings.empty(); }

void StreamReassembler::reach_eof() {
    _output.end_input();
    _unassembled_bytes = 0;
    _unassembled_strings.clear();
}

//https://paste.ubuntu.com/p/VcsFSk2XRq/